﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XNADota.Terrain;
using System.IO;
using Microsoft.Xna.Framework;
using System.Diagnostics;
using XNADota.ContentPipeline.Terrain;
using System.Drawing;

namespace XNADota.GamePlay
{
    public enum EPathType
    {
        CanWalk = 0,
        CanFly = 1,
        CanBuild = 2,
        IsBlight = 3,
        IsWater = 4,
        IsCliff = 5
    }

    public class PathManager
    {
        //Flags table:
        //0x01: 0 (unused)
        //0x02: 1=no walk, 0=walk               0 means can walk
        //0x04: 1=no fly, 0=fly                 0 means can fly
        //0x08: 1=no build, 0=build             0 means can build
        //0x10: 0 (unused)
        //0x20: 1=blight, 0=normal              0 means it's normal, 1 means undead shadow
        //0x40: 1=no water, 0=water             0 means water
        //0x80: 1=cliffs, 0=normal              0 means no cliff, 1 means cliffs

        private int _Width;
        private int _Height;
        private byte[,] _TypeData;
        private Vector3[,] _MaxHeightData;
        private Vector3[,] _MinHeightData;
        private bool[,] _IsSourceData;

        // this checks whether height are generated, after which, push height data will be forbidden
        private bool _IsHeightGenerated;

        public int Width
        {
            get { return _Width; }
        }

        public int Height
        {
            get { return _Height; }
        }

        public PathManager(Game game, LandscapeConfig landscapeConfig)
        {
            string directory = Path.Combine(game.Content.RootDirectory, Path.GetDirectoryName(landscapeConfig.ConfigFilePath));
            string path = Path.Combine(directory, landscapeConfig.WarCraftPathFileName);
            using (FileStream fileStream = new FileStream(path, FileMode.Open))
            {
                WCLoader Loader = new WCLoader(landscapeConfig.WarCraftPathFileName, fileStream);

                // file tag
                Loader.ExpectTag("MP3W");

                // version number
                Debug.Assert(Loader.ReadInt32() == 0);

                // map width & height, expect both 128
                _Width = Loader.ReadInt32();
                _Height = Loader.ReadInt32();

                //Debug.Assert(_Width == 128 && _Height == 128);

                _TypeData = new byte[_Width, _Height];
                for (int i = 0; i < _Width; i++)
                    for (int j = 0; j < _Height; j++)
                        _TypeData[i, j] = Loader.ReadByte();
            }

            _MinHeightData = new Vector3[_Width + 1, _Height + 1];
            _MaxHeightData = new Vector3[_Width + 1, _Height + 1];
            _IsSourceData = new bool[_Width + 1, _Height + 1];
            Array.Clear(_IsSourceData, 0, _IsSourceData.Length);
            //PrintDebugInfo();

            for (int i = 0; i < _Width + 1; i++)
                for (int j = 0; j < _Height + 1; j++)
                {
                    _MinHeightData[i, j] = new Vector3(i * 32.0f, 0, j * 32.0f);
                    _MaxHeightData[i, j] = new Vector3(i * 32.0f, 0, j * 32.0f);
                }

            _IsHeightGenerated = false;
        }

        private int GetXIndex(float x)
        {
            int xIndex = (int)(x / 32.0f);
            if (xIndex < 0) x = 0; if (xIndex > _Width) xIndex = _Width;
            return xIndex;
        }

        private int GetYIndex(float y)
        {
            int yIndex = (int)(y / 32.0f);
            if (yIndex < 0) y = 0; if (yIndex > _Height) yIndex = _Height;
            return yIndex;
        }

        #region height & rotation
        public Vector3 GetHeight(int xIndex, int yIndex)
        {
            return _MaxHeightData[xIndex, yIndex];
        }

        public Vector3 GetHeight(Vector3 position)
        {
            int xIndex = GetXIndex(position.X);
            int yIndex = GetYIndex(position.Z);
            return _MaxHeightData[xIndex, yIndex];
        }

        public void PushHeightData(Vector3 position, bool isCliff)
        {
            if (_IsHeightGenerated)
                throw new Exception("You should not push height data, after height data has been generated.");

            int x = GetXIndex(position.X);
            int y = GetYIndex(position.Z);

            if (_IsSourceData[x, y] == false)
            {
                _MinHeightData[x, y].Y = position.Y;
                _MaxHeightData[x, y].Y = position.Y;
                _IsSourceData[x, y] = true;
            }
            else
            {
                if (position.Y > _MaxHeightData[x, y].Y) _MaxHeightData[x, y].Y = position.Y;
                if (position.Y < _MinHeightData[x, y].Y) _MinHeightData[x, y].Y = position.Y;
            }
        }

        public void GenerateHeightData()
        {
            _IsHeightGenerated = true;

            // force set the margin data as source data
            for (int i = 0; i < _Width; i++)
            {
                _IsSourceData[i, 0] = true;
                _IsSourceData[i, _Height - 1] = true;
            }

            for (int j = 0; j < _Height; j++)
            {
                _IsSourceData[0, j] = true;
                _IsSourceData[_Width - 1, j] = true;
            }

            // generate data that are not source data
            bool changed = true;
            while (changed)
            {
                changed = false;
                for (int i = 0; i < _Width; i++)
                    for (int j = 0; j < _Height; j++)
                    {
                        if (_IsSourceData[i, j] == true)
                            continue;

                        int xLeft = (i / 4) * 4;
                        int xRight = (i / 4) * 4 + 4;
                        int yLeft = (j / 4) * 4;
                        int yRight = (j / 4) * 4 + 4;

                        float before = _MaxHeightData[i, j].Y;

                        GenerateHeightData(xLeft, j, xRight, j, i, j, 1);
                        GenerateHeightData(i, yLeft, i, yRight, i, j, 0);

                        if (Math.Abs(_MaxHeightData[i, j].Y - before) > 0.1f)
                            changed = true;
                    }
            }
        }

        private void GenerateHeightData(int x0, int y0, int x1, int y1, int x, int y, int direction)
        {
            float lerp;
            // min value
            if (direction == 1)
                lerp = (_MinHeightData[x, y].X - _MinHeightData[x0, y0].X) / (_MinHeightData[x1, y1].X - _MinHeightData[x0, y0].X);
            else
                lerp = (_MinHeightData[x, y].Z - _MinHeightData[x0, y0].Z) / (_MinHeightData[x1, y1].Z - _MinHeightData[x0, y0].Z);

            if (direction == 1)
                _MinHeightData[x, y].Y = _MinHeightData[x0, y0].Y + (_MinHeightData[x1, y1].Y - _MinHeightData[x0, y0].Y) * lerp;
            else
                _MinHeightData[x, y].Y = (_MinHeightData[x, y].Y + _MinHeightData[x0, y0].Y + (_MinHeightData[x1, y1].Y - _MinHeightData[x0, y0].Y) * lerp) / 2;

            // max value
            if (direction == 1)
                lerp = (_MaxHeightData[x, y].X - _MaxHeightData[x0, y0].X) / (_MaxHeightData[x1, y1].X - _MaxHeightData[x0, y0].X);
            else
                lerp = (_MaxHeightData[x, y].Z - _MaxHeightData[x0, y0].Z) / (_MaxHeightData[x1, y1].Z - _MaxHeightData[x0, y0].Z);

            if (direction == 1)
                _MaxHeightData[x, y].Y = _MaxHeightData[x0, y0].Y + (_MaxHeightData[x1, y1].Y - _MaxHeightData[x0, y0].Y) * lerp;
            else
                _MaxHeightData[x, y].Y = (_MaxHeightData[x, y].Y + _MaxHeightData[x0, y0].Y + (_MaxHeightData[x1, y1].Y - _MaxHeightData[x0, y0].Y) * lerp) / 2;
        }
        #endregion

        #region type
        public bool CheckType(Vector3 Position, EPathType type)
        {
            return CheckType(GetXIndex(Position.X), GetYIndex(Position.Z), type);
        }

        //Flags table:
        //0x01: 0 (unused)
        //0x02: 1=no walk, 0=walk               0 means can walk
        //0x04: 1=no fly, 0=fly                 0 means can fly
        //0x08: 1=no build, 0=build             0 means can build
        //0x10: 0 (unused)
        //0x20: 1=blight, 0=normal              0 means it's normal, 1 means undead shadow
        //0x40: 1=no water, 0=water             0 means water
        //0x80: 1=cliffs, 0=normal              0 means no cliff, 1 means cliffs
        public bool CheckType(int x, int y, EPathType type)
        {
            bool result = false;
            byte data = _TypeData[x, y];

            switch (type)
            {
                case EPathType.CanWalk:
                    result = (data & 0x02) == 0;
                    break;
                case EPathType.CanFly:
                    result = (data & 0x04) == 0;
                    break;
                case EPathType.CanBuild:
                    result = (data & 0x08) == 0;
                    break;
                case EPathType.IsBlight:
                    result = (data & 0x20) != 0;
                    break;
                case EPathType.IsWater:
                    result = (data & 0x40) == 0;
                    break;
                case EPathType.IsCliff:
                    result = (data & 0x80) == 1;
                    break;
            }

            return result;
        }
        #endregion // type

        #region debug info
        private Color GetDebugPrintColor(int x, int y)
        {
            Color color = Color.Black;
            if (!Convert.ToBoolean(_TypeData[x, y] & 0x2))
                color = Color.Green;

            if (!Convert.ToBoolean(_TypeData[x, y] & 0x4))
            {
                if (color == Color.Black)
                    color = Color.Yellow;
                else
                    color = Color.Blue;
            }

            return color;
        }

        public void PrintDebugInfo()
        {
            using (Bitmap bmp = new Bitmap(_Width, _Height))
            {
                for (int x = 0; x < _Width; x++)
                {
                    for (int y = 0; y < _Height; y++)
                    {
                        bmp.SetPixel(y, _Width - x - 1, GetDebugPrintColor(x, y));
                    }
                }

                bmp.Save("PathDebug.bmp");
            }
        }
        #endregion
    }
}
